#include <cstdint>
#include <cstdlib>
#include <cstring>

#include <iostream>
#include <string>

namespace xoxyy {
class String {
 private:
  char* buffer;
  explicit String();  // default constructor

 public:
  explicit String(char const* str);      // constructor
  String(String const& str);             // copy constructor
  String& operator=(String const& str);  // copy assignment op
  String(String&& str);                  // move constructor
  String& operator=(String&& str);       // move assignment op
  ~String();                             // destructor
  int64_t Len();
  String operator+(String const& str);
};
};

xoxyy::String::String() : buffer(NULL) {
  std::cout << "xoxyy::String::String()" << std::endl;
  return;
}

xoxyy::String::String(char const* str) {
  std::cout << "xoxyy::String::String(char const *str)" << std::endl;
  if (str == NULL) {
    this->buffer = new char[1];
    this->buffer[0] = '\0';
    return;
  }
  auto len = (int64_t)strlen(str);
  this->buffer = new char[len + 1];
  strcpy(this->buffer, str);
  this->buffer[len] = '\0';
  return;
}

xoxyy::String::String(String const& str) : buffer(NULL) {
  std::cout << "xoxyy::String::String(String const& str)" << std::endl;
  if (str.buffer == NULL) {
    this->buffer = new char[1];
    this->buffer[0] = '\0';
    return;
  }
  auto len = (int64_t)strlen(str.buffer);
  this->buffer = new char[len + 1];
  strcpy(this->buffer, str.buffer);
  this->buffer[len] = '\0';
  return;
}

xoxyy::String& xoxyy::String::operator=(xoxyy::String const& str) {
  std::cout
      << "xoxyy::String& xoxyy::String::operator=(xoxyy::String const& str)"
      << std::endl;
  if (str.buffer == NULL) {
    return *this;
  }

  auto len = (int64_t)strlen(this->buffer);
  this->buffer = new char[len + 1];
  strcpy(this->buffer, str.buffer);
  this->buffer[len] = '\0';
  return *this;
}

xoxyy::String::String(xoxyy::String&& str) {
  std::cout << "xoxyy::String::String (xoxyy::String&& str)" << std::endl;
  if (str.buffer == NULL) {
    this->buffer = new char[1];
    this->buffer[0] = '\0';
    return;
  }
  if (str.buffer != NULL) {
    delete[] this->buffer;
    this->buffer = NULL;
  }
  this->buffer = str.buffer;
  str.buffer = NULL;
  return;
}

xoxyy::String& xoxyy::String::operator=(xoxyy::String&& str) {
  std::cout << "xoxyy::String& xoxyy::String::operator=(xoxyy::String&& str)"
            << std::endl;
  if (str.buffer == NULL) {
    this->buffer = new char[1];
    this->buffer[0] = '\0';
    return *this;
  }
  if (str.buffer != NULL) {
    delete[] this->buffer;
    this->buffer = NULL;
  }
  this->buffer = str.buffer;
  str.buffer = NULL;
  return *this;
}

xoxyy::String::~String() {
  std::cout << "xoxyy::String::~String()" << std::endl;
  if (this->buffer != NULL) {
    delete[] this->buffer;
    this->buffer = NULL;
  }
  return;
}

int64_t xoxyy::String::Len() {
  std::cout << "int64_t xoxyy::String::Len()" << std::endl;
  return (int64_t)strlen(this->buffer);
}

xoxyy::String xoxyy::String::operator+(xoxyy::String const& str) {
  std::cout
      << "xoxyy::String xoxyy::String::operator+ (xoxyy::String const & str)"
      << std::endl;
  if (str.buffer == NULL) {
    return *this;
  }
  auto len1 = strlen(this->buffer);
  auto len2 = strlen(str.buffer);
  char* newBuffer = new char[len1 + len2 + 1];
  strcpy(newBuffer, this->buffer);
  strcat(newBuffer, str.buffer);
  newBuffer[len1 + len2] = '\0';
  delete[] this->buffer;
  this->buffer = NULL;
  this->buffer = newBuffer;
  return *this;
}

int main(int argc, char const* argv[]) {
  xoxyy::String s1("Hello, ");
  xoxyy::String s2("world");
  xoxyy::String s3(" of C++ !");

  xoxyy::String s4("overwrite this !");
  s4 = s1 + s2 + s3;

  std::cout << s4.Len() << std::endl;

  return EXIT_SUCCESS;
}